#include "primitives.hpp"

#include <stdexcept>
#include <cmath>

using namespace std;

/*Primitive::Primitive()
{
}*/

SurfacePrimitive::SurfacePrimitive()
{
	frontShader_ = NULL;
	backShader_ = NULL;
}

void SurfacePrimitive::setSurfaceShader(const SurfaceShader *shader)
{
	frontShader_ = shader;
	backShader_ = shader;
//	backShader_ = new DebugShader(); //FIXME!!! DEBUG ONLY
}

Color SurfacePrimitive::shade(const SampleInfo& sample, const Scene& scene, const Renderer& renderer) const
{
	if(sample.miscInt == FRONT_INTERSECTION)
		return frontShader_->shade(sample, scene, renderer);
	else if (sample.miscInt == BACK_INTERSECTION)
		return backShader_->shade(sample, scene, renderer);
	return Color();
}

///////////////////////////////////////////////////////////////////////////////
//           Sphere primitive
//

Sphere::Sphere(const Vector3& origin, double radius) :
	origin_(origin), radius_(radius)
{
}

bool Sphere::intersect(const Ray& ray, double dist, SampleInfo& sample) const
{
	Vector3 v = ray.getOrigin() - origin_;
	double b = - dot(v, ray.getDirection());
	double det = (b * b) - dot(v, v) + radius_;
	if(det <= 0.0)
		return Primitive::NO_INTERSECTION;
	det = sqrt(det);
	double i1 = b - det;
	double i2 = b + det;
	if(i1 > 0.0 && i1 < dist && frontShader_ != NULL)
	{
		sample.dist = i1;
		sample.miscInt = Primitive::FRONT_INTERSECTION;
		return true;
	}
	if(backShader_ != NULL && i2 > 0 && i2 < dist)
	{
		sample.dist = i2;
		sample.miscInt = Primitive::BACK_INTERSECTION;
		return true;
	}
	return false;
}

Vector3 Sphere::getNormal(const SampleInfo& sample, SurfacePrimitive::NormalType type) const
{
	return normalize(sample.position - origin_);
}

Vector3 Sphere::getUVW(const SampleInfo& sample) const
{
	double cosa = dot(normalize(sample.position - origin_), Vector3(1.0, 0.0, 0.0));
	double cosb = dot(normalize(sample.position - origin_), Vector3(0.0, 1.0, 0.0));
	double cosc = dot(normalize(sample.position - origin_), Vector3(0.0, 0.0, 1.0));
	return Vector3(acos(cosa), acos(cosb), acos(cosc));
}

const char* Sphere::getType() const
{
	return "sphere";
}

///////////////////////////////////////////////////////////////////////////////
//           Plane primitive
//

Plane::Plane(const Vector3& origin, const Vector3& normal) :
	origin_(origin), normal_(normalize(normal))
{
}

bool Plane::intersect(const Ray& ray, double dist, SampleInfo& sample) const
{
	Vector3 v = origin_ - ray.getOrigin();
	double d = dot(normal_, v);
	double e = dot(normal_, ray.getDirection());
	
	if(fabs(e) < 1e-20)
		return false;
	double i = d / e;
	if((i > dist) || (i < 0.0))
		return false;

	if(frontShader_ != NULL && e < 0)
	{
		sample.dist = i;
		sample.miscInt = Primitive::FRONT_INTERSECTION;
		return true;
	}
	if(backShader_ != NULL)
	{
		sample.dist = i;
		sample.miscInt = Primitive::BACK_INTERSECTION;
		return true;
	}
	return false;
}

Vector3 Plane::getNormal(const SampleInfo& sample, SurfacePrimitive::NormalType type) const
{
	return normal_;
}

Vector3 Plane::getUVW(const SampleInfo& sample) const
{
	return Vector3(0.0, 0.0, 0.0);
}

const char* Plane::getType() const
{
	return "plane";
}

///////////////////////////////////////////////////////////////////////////////
//           Triangle primitive
//

Triangle::Triangle(int triID, const TriBuffer * buf)
{
	triID_ = triID;
	int id0 = buf->vertIDs[triID].id0;
	int id1 = buf->vertIDs[triID].id1;
	int id2 = buf->vertIDs[triID].id2;
	origin_ = buf->vertices[id0];
	Vector3 v1 = buf->vertices[id1] - origin_;
	Vector3 v2 = buf->vertices[id2] - origin_;
	normal_ = normalize(v1 * v2);
	m_ = inverse(Matrix3(v1, v2, normal_));
}

bool Triangle::intersect(const Ray& ray, double dist, SampleInfo& sample) const
{
	Vector3 v = origin_ - ray.getOrigin();
	double d = dot(normal_, v);
	double e = dot(normal_, ray.getDirection());
	
	if(fabs(e) < 1e-20)
		return false;
	double i = d / e;
	if((i > dist) || (i < 0.0))
		return false;

	if(frontShader_ != NULL && e < 0)
	{
		Vector3 position = ray.getOrigin() + ray.getDirection() * i;
		Vector3 weights;
		if(innerIntersect(position, weights))
		{
			sample.dist = i;
			sample.miscInt = Primitive::FRONT_INTERSECTION;
			sample.miscVector3 = weights;
			return true;
		}
		return false;
	}
	if(backShader_ != NULL)
	{
		Vector3 position = ray.getOrigin() + ray.getDirection() * i;
		Vector3 weights;
		if(innerIntersect(position, weights))
		{
			sample.dist = i;
			sample.miscInt = Primitive::BACK_INTERSECTION;
			sample.miscVector3 = weights;
			return true;
		}
		return false;
	}
	return false;
}

bool Triangle::innerIntersect(const Vector3& position, Vector3& weights) const
{
	Vector3 temp = m_ * (position - origin_);
	temp.z = 1 - temp.x - temp.y;
	if(temp.x < 0.0 || temp.x > 1.0 || temp.y < 0.0 || temp.y > 1.0 || temp.z < 0.0 || temp.z > 1.0)
		return false;
	weights = temp;
	return true;
}

Vector3 Triangle::getNormal(const SampleInfo& sample, SurfacePrimitive::NormalType type) const
{
	return normal_;
}

Vector3 Triangle::getUVW(const SampleInfo& sample) const
{
	return Vector3(0.0, 0.0, 0.0);
}

const char* Triangle::getType() const
{
	return "triangle";
}
